#include "wingcoroutine.h"
#include "wingcoroutine_p.h"

#include <ctime>

using namespace Wing;

WingCoroutine::WingCoroutine(size_t stackSize)
    : d_ptr(new WingCoroutine_p(stackSize)) {
  d_ptr->_hostThread = std::thread([this] {
    ThreadInfo th0;
    th0.id = 0;
    th0.pc = nullptr;
    th0.stackPC = nullptr;
    asm("movq %%rbp,%0" ::"m"(th0.stack));
    th0.state = ThreadState::RUNNING;
    th0.functor = nullptr;
    this->d_ptr->_threads.push_back(th0);
    this->d_ptr->_currentThread = this->d_ptr->_threads.begin();
    d_ptr->_sm.release();

    while (this->d_ptr->_keepRunning) {
      d_ptr->swapContextCaller(this);
    }
  });
  d_ptr->_sm.acquire();
}

WingCoroutine::~WingCoroutine() {
  this->d_ptr->_keepRunning = 0;
  d_ptr->_hostThread.join();
}

const std::thread::id WingCoroutine::hostThreadID() const {
  return d_ptr->_hostThread.get_id();
}

long WingCoroutine::createThread(ThreadFunctor functor, void *params) {
  if (this->d_ptr->_hostThread.get_id() == std::thread::id()) {
    return false;
  }

  if (functor == nullptr) {
    return false;
  }

  auto stack = new unsigned char[d_ptr->stackSize()];
  // align stack frame for idle thread code address(x64 call convention)
  auto stackPC = reinterpret_cast<void *>(
      (size_t(stack + d_ptr->stackSize()) & (UINTPTR_MAX - 0xF)) - 8);

  ThreadInfo th;
  th.id = this->d_ptr->getID();
  th.functor = functor;
  th.pc = reinterpret_cast<void *>(functor);
  th.param = params;
  th.stack = reinterpret_cast<void *>(stack);
  th.stackPC = stackPC;
  th.state = ThreadState::READY;
  d_ptr->_threads.push_back(th);
  return true;
}

bool WingCoroutine::sendCOAPC(const CO_APC &co_apc) {
  return this->d_ptr->sendCOAPC(co_apc);
}

bool WingCoroutine::stopThread(long id) {
  if (this->d_ptr->_hostThread.get_id() == std::thread::id()) {
    return false;
  }

  if (id == 0) {
    return false;
  }

  auto th =
      std::find_if(this->d_ptr->_threads.begin(), this->d_ptr->_threads.end(),
                   [=](const ThreadInfo &info) { return info.id == id; });
  if (th == this->d_ptr->_threads.end()) {
    return false;
  }

  CO_APC co_apc = std::bind(
      [this](std::list<ThreadInfo>::iterator &th) {
        th->state = ThreadState::DEAD;
      },
      th);

  return sendCOAPC(co_apc);
}

bool WingCoroutine::stopAllThread() {
  if (this->d_ptr->_hostThread.get_id() == std::thread::id()) {
    return false;
  }

  CO_APC co_apc = std::function([this]() {
    for (auto &th : this->d_ptr->_threads) {
      if (th.id == 0) {
        continue;
      }
      th.state = ThreadState::DEAD;
    }
  });
  return true;
}

const ThreadInfo WingCoroutine::getThreadInfo(long id) const {
  auto res =
      std::find_if(this->d_ptr->_threads.begin(), this->d_ptr->_threads.end(),
                   [=](const ThreadInfo &info) { return info.id == id; });
  if (res == this->d_ptr->_threads.end()) {
    return ThreadInfo();
  }
  return *res;
}

const std::list<ThreadInfo> WingCoroutine::getAllThreads() {
  return d_ptr->_threads;
}

void WingCoroutine::co_yeild(WingCoroutine *self) {
  if (self == nullptr) {
    return;
  }
  self->d_ptr->swapContextCaller(self);
}
